package async

import (
	"context"
	"gitee.com/chicken-c/go-learn/webook/internal/domain"
	"gitee.com/chicken-c/go-learn/webook/internal/repostiory"
	"gitee.com/chicken-c/go-learn/webook/internal/service/sms"
	"gitee.com/chicken-c/go-learn/webook/pkg/logger"
	"sync"
	"sync/atomic"
	"time"
)

type Service struct {
	//装饰器模式
	svc  sms.ServiceBatch
	repo repostiory.AsyncSmsRepository
	l    logger.Logger

	// 新增字段（原子操作保证并发安全）
	consecutiveFailures int32     // 连续失败计数器
	lastFailureTime     time.Time // 最后一次失败时间（可选，用于重置逻辑）
	mu                  sync.Mutex
}

func (s *Service) StartAsyncCycle() {
	for {
		s.AsyncSend()
	}
}

func (s *Service) AsyncSend() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	as, err := s.repo.PreemptWaitingSMS(ctx)
	cancel()

	switch err {
	case nil:
		//执行发送任务
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		er := s.svc.Send(ctx, as.TqlId, as.Args, as.Numbers...)
		if er != nil {
			s.l.Error("执行异步发送短信失败",
				logger.Field{
					Key: "err",
					Val: er,
				},
				logger.Field{
					Key: "id",
					Val: as.Id,
				})
		}

		res := err == nil
		er = s.repo.ReportScheduleResult(ctx, as.Id, res)
		if er != nil {
			s.l.Error("执行异步发送短信成功，但是标记数据库失败",
				logger.Field{
					Key: "err",
					Val: er,
				},
				logger.Field{
					Key: "id",
					Val: as.Id,
				})
		}

	case repostiory.ErrSMSNotFound:
		//没有需要异步处理的短信
		//睡上一秒
		time.Sleep(time.Second)
	default:
		//一般都是数据库出了问题，或者是网络抖动
		s.l.Error("抢占异步发送短信失败", logger.Field{
			Key: "err",
			Val: err,
		})
		time.Sleep(time.Second)
	}
}

func (s *Service) Send(ctx context.Context, tqlId string, args []string, number ...string) error {
	if s.needAsync() {
		//转异步
		err := s.repo.Add(ctx, domain.AsyncSms{
			TqlId:   tqlId,
			Args:    args,
			Numbers: number,
			// 设置可以重试三次
			RetryMax: 3,
		})
		return err
	}

	//同步发送
	err := s.svc.Send(ctx, tqlId, args, number...)
	if err != nil {
		// 失败时递增计数器并记录时间
		atomic.AddInt32(&s.consecutiveFailures, 1)
		s.mu.Lock()
		s.lastFailureTime = time.Now()
		s.mu.Unlock()
	} else {
		// 成功时重置计数器
		atomic.StoreInt32(&s.consecutiveFailures, 0)
	}
	return err
}

func (s *Service) needAsync() bool {
	//这里就是我们的各种判断服务商出错的策略

	// 原子读取当前失败次数
	failures := atomic.LoadInt32(&s.consecutiveFailures)

	// 如果 >=3 次直接触发异步
	if failures >= 3 {
		return true
	}

	// 超时自动重置（30分钟内无新失败则重置）
	s.mu.Lock()
	defer s.mu.Unlock()
	if time.Since(s.lastFailureTime) > 30*time.Minute {
		atomic.StoreInt32(&s.consecutiveFailures, 0)
		return false
	}

	return false
}
